#region The MIT License
// The MIT License
// 
// Copyright (c) 2011 octalforty studios
// 
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
// 
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
#endregion
using System;
using System.Diagnostics;
using System.Net.Mail;
using System.Text;

using octalforty.Staytuned.ObjectModel;
using octalforty.Staytuned.Resources;

namespace octalforty.Staytuned.Impl.ComponentModel.Email
{
    /// <summary>
    /// Provides support for sending notification messages via email.
    /// </summary>
    /// <remarks>
    /// In order to have a more friendly "From" addresses in outgoing email messages, follow these rules:
    /// <list type="bullet">
    ///     <item>Specify full sender address in <c>web.config</c> (like <c>Support &lt;support@example.com></c></item>
    ///     <item>Override sender name when calling <see cref="PublishMessage"/> by setting <see cref="SenderDescriptor.Name"/> of 
    ///     <see cref="Message.Sender"/> to an appropriate value and by setting <see cref="SenderDescriptor.Address"/> to <c>null</c></item>
    /// </list>
    /// </remarks>
    public class EmailNotificationSupportModule : NotificationSupportModule
    {
        #region Private Members
        private readonly bool enableSsl;
        private readonly object syncRoot = new object();
        #endregion

        /// <summary>
        /// Initializes a new instance of the <see cref="EmailNotificationSupportModule"/> class.
        /// </summary>
        /// <param name="enableSsl">Specifies whether the underlying <see cref="SmtpClient"/> uses SSL to encrypt
        /// the connection.</param>
        public EmailNotificationSupportModule(bool enableSsl) :
            this(new[] { "email" }, enableSsl)
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="EmailNotificationSupportModule"/> class.
        /// </summary>
        /// <param name="enableSsl">Specifies whether the underlying <see cref="SmtpClient"/> uses SSL to encrypt the connection.</param>
        /// <param name="supportedMessageTypes"></param>
        /// <param name="supportedSchemes"></param>
        protected EmailNotificationSupportModule(string[] supportedSchemes, bool enableSsl) : 
            base(supportedSchemes)
        {
            Log.Info("initializing with SSL {0}", enableSsl ? "enabled" : "disabled");

            this.enableSsl = enableSsl;
        }

        public override void PublishMessage(Message message, string address)
        {
            //
            // EnableSsl is only available for SmtpDeliveryMethod.Network
            var smtpClient = new SmtpClient();
            smtpClient.EnableSsl = smtpClient.DeliveryMethod == SmtpDeliveryMethod.Network && enableSsl;
                
            var mailMessage = new MailMessage {
                To = { ParseEmailAddress(address) },
                Subject = message.Subject,
                SubjectEncoding = Encoding.UTF8,
                Body = message.Body,
                BodyEncoding = Encoding.UTF8
            };

            if(message.ReplyTo != null)
            {
                Log.Debug("using '{0}' as reply-to address", message.ReplyTo.EffectiveAddress);
                mailMessage.ReplyTo = ParseEmailAddress(message.ReplyTo.EffectiveAddress);
            } // if

            foreach(var header in message.Headers)
                mailMessage.Headers.Add(header.Key, header.Value);

            if(message.Sender != null)
            {
                if(message.Sender.Address != null)
                {
                    Log.Debug("using '{0}' as from address", message.Sender.Address.EffectiveAddress);
                    mailMessage.From = ParseEmailAddress(message.Sender.Address.EffectiveAddress);
                } // if
                else if(!string.IsNullOrEmpty(message.Sender.Name))
                {
                    var fromAddress = string.Format("{0} <{1}>", message.Sender.Name.Replace("\"", ""), mailMessage.From.Address);
                    
                    Log.Debug("using '{0}' as from address", fromAddress);
                    mailMessage.From = ParseEmailAddress(fromAddress);
                } // else if
            } // if

            foreach(var attachment in message.Attachments)
                mailMessage.Attachments.Add(
                    new System.Net.Mail.Attachment(attachment.ContentStreamProvider(), attachment.Name, attachment.ContentType));

            try
            {
                smtpClient.Send(mailMessage);
            } // try
            catch(SmtpFailedRecipientException e)
            {
                Log.Error("could not publish message: {0}", e.ToString());
                throw new PublishMessageException(string.Format(Exceptions.CouldNotDeliverMailMessageToAddress, address), e);
            } // catch
            catch(SmtpException e)
            {
                Log.Error("could not publish message: {0}", e.ToString());
                throw new PublishMessageException(string.Format(Exceptions.CouldNotCommunicateWithSmtpServer, smtpClient.Host), e);
            } // catch
            finally
            {
                mailMessage.Dispose();
            } // finally
        }

        private MailAddress ParseEmailAddress(string address)
        {
            try
            {
                Log.Trace("attempting to parse '{0}' as email address", address);
                return new MailAddress(address);
            } // try
            catch(FormatException e)
            {
                Log.Error("could not parse email address '{0}'", address);
                throw new FormatException(string.Format(Exceptions.CouldNotParseEmailAddress, address), e);
            } // catch
        }
    }
}
